/*-------------------------------------------------------------------------
 *
 * ginbtree.c
 *	  postgres 倒排索引访问方法的页面实用例程。
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *			src/backend/access/gin/ginbtree.c
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "access/gin_private.h"
#include "access/ginxlog.h"
#include "access/xloginsert.h"
#include "miscadmin.h"
#include "storage/predicate.h"
#include "utils/memutils.h"
#include "utils/rel.h"

static void fc_ginFindParents(GinBtree fc_btree, GinBtreeStack *fc_stack);
static bool fc_ginPlaceToPage(GinBtree fc_btree, GinBtreeStack *fc_stack,
						   void *fc_insertdata, BlockNumber fc_updateblkno,
						   Buffer fc_childbuf, GinStatsData *fc_buildStats);
static void fc_ginFinishSplit(GinBtree fc_btree, GinBtreeStack *fc_stack,
						   bool fc_freestack, GinStatsData *fc_buildStats);
static void fc_ginFinishOldSplit(GinBtree fc_btree, GinBtreeStack *fc_stack,
							  GinStatsData *fc_buildStats, int fc_access);

/*
 * 通过需要的方法锁定缓冲区以进行搜索。
 */
int ginTraverseLock(Buffer fc_buffer, bool fc_searchMode)
{
	Page		fc_page;
	int			fc_access = GIN_SHARE;

	LockBuffer(fc_buffer, GIN_SHARE);
	fc_page = BufferGetPage(fc_buffer);
	if (GinPageIsLeaf(fc_page))
	{
		if (fc_searchMode == false)
		{
			/* 我们应该重新锁定我们的页面 */
			LockBuffer(fc_buffer, GIN_UNLOCK);
			LockBuffer(fc_buffer, GIN_EXCLUSIVE);

			/* 但在重新锁定期间，根可以变为非叶子 */
			if (!GinPageIsLeaf(fc_page))
			{
				/* 恢复旧的锁定类型（非常少见） */
				LockBuffer(fc_buffer, GIN_UNLOCK);
				LockBuffer(fc_buffer, GIN_SHARE);
			}
			else
				fc_access = GIN_EXCLUSIVE;
		}
	}

	return fc_access;
}

/*
 * 向下遍历树到包含或将包含我们正在搜索的键的叶子页面。
 * 该键应已经以特定于树型的方式填充在 'btree' 中。如果 btree->fullScan 为真，则向最左边的叶子页面下降。
 *
 * 如果 'searchmode' 为假，返回时 stack->buffer 被独占锁定，
 * 栈表示到根的完整路径。否则 stack->buffer 是共享锁定，stack->parent 为 NULL。
 *
 * 如果 'rootConflictCheck' 为真，则检查树根以确定序列化冲突。
 */
GinBtreeStack *
ginFindLeafPage(GinBtree fc_btree, bool fc_searchMode,
				bool fc_rootConflictCheck, Snapshot fc_snapshot)
{
	GinBtreeStack *fc_stack;

	fc_stack = (GinBtreeStack *) palloc(sizeof(GinBtreeStack));
	fc_stack->blkno = fc_btree->rootBlkno;
	fc_stack->buffer = ReadBuffer(fc_btree->index, fc_btree->rootBlkno);
	fc_stack->parent = NULL;
	fc_stack->predictNumber = 1;

	if (fc_rootConflictCheck)
		CheckForSerializableConflictIn(fc_btree->index, NULL, fc_btree->rootBlkno);

	for (;;)
	{
		Page		fc_page;
		BlockNumber fc_child;
		int			fc_access;

		fc_stack->off = InvalidOffsetNumber;

		fc_page = BufferGetPage(fc_stack->buffer);
		TestForOldSnapshot(fc_snapshot, fc_btree->index, fc_page);

		fc_access = ginTraverseLock(fc_stack->buffer, fc_searchMode);

		/*
		 * 如果我们将要修改树，完成在途中遇到的任何不完整拆分。
		 */
		if (!fc_searchMode && GinPageIsIncompleteSplit(fc_page))
			fc_ginFinishOldSplit(fc_btree, fc_stack, NULL, fc_access);

		/*
		 * 好的，页面已正确锁定，我们应该检查向右移动 ..,
		 * 根永远没有右链接，所以一点小优化
		 */
		while (fc_btree->fullScan == false && fc_stack->blkno != fc_btree->rootBlkno &&
			   fc_btree->isMoveRight(fc_btree, fc_page))
		{
			BlockNumber fc_rightlink = GinPageGetOpaque(fc_page)->rightlink;

			if (fc_rightlink == InvalidBlockNumber)
				/* 最右边的页面 */
				break;

			fc_stack->buffer = ginStepRight(fc_stack->buffer, fc_btree->index, fc_access);
			fc_stack->blkno = fc_rightlink;
			fc_page = BufferGetPage(fc_stack->buffer);
			TestForOldSnapshot(fc_snapshot, fc_btree->index, fc_page);

			if (!fc_searchMode && GinPageIsIncompleteSplit(fc_page))
				fc_ginFinishOldSplit(fc_btree, fc_stack, NULL, fc_access);
		}

		if (GinPageIsLeaf(fc_page))	/* 我们找到了，返回锁定的页面 */
			return fc_stack;

		/* 现在我们有了正确的缓冲区，尝试查找子项 */
		fc_child = fc_btree->findChildPage(fc_btree, fc_stack);

		LockBuffer(fc_stack->buffer, GIN_UNLOCK);
		Assert(fc_child != InvalidBlockNumber);
		Assert(fc_stack->blkno != fc_child);

		if (fc_searchMode)
		{
			/* 在搜索模式下，我们可能会忘记到达叶子的路径 */
			fc_stack->blkno = fc_child;
			fc_stack->buffer = ReleaseAndReadBuffer(fc_stack->buffer, fc_btree->index, fc_stack->blkno);
		}
		else
		{
			GinBtreeStack *fc_ptr = (GinBtreeStack *) palloc(sizeof(GinBtreeStack));

			fc_ptr->parent = fc_stack;
			fc_stack = fc_ptr;
			fc_stack->blkno = fc_child;
			fc_stack->buffer = ReadBuffer(fc_btree->index, fc_stack->blkno);
			fc_stack->predictNumber = 1;
		}
	}
}

/*
 * 从当前页面向右一步。
 *
 * 首先锁定下一个页面，然后释放当前页面。这对防止并发的 VACUUM 删除我们即将跨越的页面至关重要。
 * （在我们遍历树以查找插入位置时，锁定耦合并非绝对必要，因为页面删除会在根上获取清理锁，以防止任何并发插入。请参阅 README 中的页面删除部分。 但总是这样做也无妨。）
 */
Buffer ginStepRight(Buffer fc_buffer, Relation fc_index, int fc_lockmode)
{
	Buffer		fc_nextbuffer;
	Page		fc_page = BufferGetPage(fc_buffer);
	bool		fc_isLeaf = GinPageIsLeaf(fc_page);
	bool		fc_isData = GinPageIsData(fc_page);
	BlockNumber fc_blkno = GinPageGetOpaque(fc_page)->rightlink;

	fc_nextbuffer = ReadBuffer(fc_index, fc_blkno);
	LockBuffer(fc_nextbuffer, fc_lockmode);
	UnlockReleaseBuffer(fc_buffer);

	/* sanity 检查我们跨越到的页面是否相似。 */
	fc_page = BufferGetPage(fc_nextbuffer);
	if (fc_isLeaf != GinPageIsLeaf(fc_page) || fc_isData != GinPageIsData(fc_page))
		elog(ERROR, "right sibling of GIN page is of different type");

	return fc_nextbuffer;
}

void freeGinBtreeStack(GinBtreeStack *fc_stack)
{
	while (fc_stack)
	{
		GinBtreeStack *fc_tmp = fc_stack->parent;

		if (fc_stack->buffer != InvalidBuffer)
			ReleaseBuffer(fc_stack->buffer);

		pfree(fc_stack);
		fc_stack = fc_tmp;
	}
}

/*
 * 尝试找到当前栈位置的父项。返回正确的父项和子项在 stack->parent 中的偏移量。
 * 根页面永远不会被释放，以防止与清理过程发生冲突。
 */
static void fc_ginFindParents(GinBtree fc_btree, GinBtreeStack *fc_stack)
{
	Page		fc_page;
	Buffer		fc_buffer;
	BlockNumber fc_blkno,
				fc_leftmostBlkno;
	OffsetNumber fc_offset;
	GinBtreeStack *fc_root;
	GinBtreeStack *fc_ptr;

	/*
	 * 将栈回退到根，仅保留根项目。
	 *
	 * 小心不要释放根页面上的锁定！根页面上的锁定是为了阻止并发的 VACUUM 操作。
	 */
	fc_root = fc_stack->parent;
	while (fc_root->parent)
	{
		ReleaseBuffer(fc_root->buffer);
		fc_root = fc_root->parent;
	}

	Assert(fc_root->blkno == fc_btree->rootBlkno);
	Assert(BufferGetBlockNumber(fc_root->buffer) == fc_btree->rootBlkno);
	fc_root->off = InvalidOffsetNumber;

	fc_blkno = fc_root->blkno;
	fc_buffer = fc_root->buffer;

	fc_ptr = (GinBtreeStack *) palloc(sizeof(GinBtreeStack));

	for (;;)
	{
		LockBuffer(fc_buffer, GIN_EXCLUSIVE);
		fc_page = BufferGetPage(fc_buffer);
		if (GinPageIsLeaf(fc_page))
			elog(ERROR, "Lost path");

		if (GinPageIsIncompleteSplit(fc_page))
		{
			Assert(fc_blkno != fc_btree->rootBlkno);
			fc_ptr->blkno = fc_blkno;
			fc_ptr->buffer = fc_buffer;

			/*
			 * 父项可能是错误的，但如果是这样，ginFinishSplit 调用将递归调用 ginFindParents 来修复它。
			 */
			fc_ptr->parent = fc_root;
			fc_ptr->off = InvalidOffsetNumber;

			fc_ginFinishOldSplit(fc_btree, fc_ptr, NULL, GIN_EXCLUSIVE);
		}

		fc_leftmostBlkno = fc_btree->getLeftMostChild(fc_btree, fc_page);

		while ((fc_offset = fc_btree->findChildPtr(fc_btree, fc_page, fc_stack->blkno, InvalidOffsetNumber)) == InvalidOffsetNumber)
		{
			fc_blkno = GinPageGetOpaque(fc_page)->rightlink;
			if (fc_blkno == InvalidBlockNumber)
			{
				/* 该层中不存在链接 */
				LockBuffer(fc_buffer, GIN_UNLOCK);
				/* 不要释放根缓冲区上的锁定 */
				if (fc_buffer != fc_root->buffer)
					ReleaseBuffer(fc_buffer);
				break;
			}
			fc_buffer = ginStepRight(fc_buffer, fc_btree->index, GIN_EXCLUSIVE);
			fc_page = BufferGetPage(fc_buffer);

			/* 按上述方法完成任何不完整的拆分 */
			if (GinPageIsIncompleteSplit(fc_page))
			{
				Assert(fc_blkno != fc_btree->rootBlkno);
				fc_ptr->blkno = fc_blkno;
				fc_ptr->buffer = fc_buffer;
				fc_ptr->parent = fc_root;
				fc_ptr->off = InvalidOffsetNumber;

				fc_ginFinishOldSplit(fc_btree, fc_ptr, NULL, GIN_EXCLUSIVE);
			}
		}

		if (fc_blkno != InvalidBlockNumber)
		{
			fc_ptr->blkno = fc_blkno;
			fc_ptr->buffer = fc_buffer;
			fc_ptr->parent = fc_root; /* 这可能是错误的，但在下一个调用中我们将
								 * 进行更正 */
			fc_ptr->off = fc_offset;
			fc_stack->parent = fc_ptr;
			return;
		}

		/* 向下到下一级 */
		fc_blkno = fc_leftmostBlkno;
		fc_buffer = ReadBuffer(fc_btree->index, fc_blkno);
	}
}

/*
 * 将新项目插入页面。
 *
 * 如果插入完成，则返回true。如果返回false，表示页面已被拆分，
 * 需要更新父页面。 （根拆分返回true，因为它不需要调用者进行
 * 任何进一步的操作来完成。）
 *
 * 在向内部页面插入下行链接时，'childbuf'包含被拆分的子页面。
 * 它的GIN_INCOMPLETE_SPLIT标志将在插入时原子性地被清除。
 * 此外，目标页面中偏移量stack->off处的现有项指向updateblkno。
 *
 * stack->buffer在进入时被锁定，并保持锁定状态。
 * 如果提供了childbuf，同样适用。
 */
static bool fc_ginPlaceToPage(GinBtree fc_btree, GinBtreeStack *fc_stack,
			   void *fc_insertdata, BlockNumber fc_updateblkno,
			   Buffer fc_childbuf, GinStatsData *fc_buildStats)
{
	Page		fc_page = BufferGetPage(fc_stack->buffer);
	bool		fc_result;
	GinPlaceToPageRC fc_rc;
	uint16		fc_xlflags = 0;
	Page		fc_childpage = NULL;
	Page		fc_newlpage = NULL,
				fc_newrpage = NULL;
	void	   *fc_ptp_workspace = NULL;
	MemoryContext fc_tmpCxt;
	MemoryContext fc_oldCxt;

	/*
	 * 我们在一个临时内存上下文中完成此函数及其子函数的所有工作。
	 * 这样可以避免泄漏并简化API，因为一些子函数分配的存储
	 * 需要在我们完成WAL插入之前存活。
	 */
	fc_tmpCxt = AllocSetContextCreate(CurrentMemoryContext,
								   "ginPlaceToPage temporary context",
								   ALLOCSET_DEFAULT_SIZES);
	fc_oldCxt = MemoryContextSwitchTo(fc_tmpCxt);

	if (GinPageIsData(fc_page))
		fc_xlflags |= GIN_INSERT_ISDATA;
	if (GinPageIsLeaf(fc_page))
	{
		fc_xlflags |= GIN_INSERT_ISLEAF;
		Assert(!BufferIsValid(fc_childbuf));
		Assert(fc_updateblkno == InvalidBlockNumber);
	}
	else
	{
		Assert(BufferIsValid(fc_childbuf));
		Assert(fc_updateblkno != InvalidBlockNumber);
		fc_childpage = BufferGetPage(fc_childbuf);
	}

	/*
	 * 查看传入的元组是否适合页面。 beginPlaceToPage将决定
	 * 页面是否需要拆分，并在需要时计算拆分内容。
	 * 有关此处API的更多详细信息，请参阅beginPlaceToPage和
	 * execPlaceToPage函数的注释。
	 */
	fc_rc = fc_btree->beginPlaceToPage(fc_btree, fc_stack->buffer, fc_stack,
								 fc_insertdata, fc_updateblkno,
								 &fc_ptp_workspace,
								 &fc_newlpage, &fc_newrpage);

	if (fc_rc == GPTP_NO_WORK)
	{
		/* 无需执行任何操作 */
		fc_result = true;
	}
	else if (fc_rc == GPTP_INSERT)
	{
		/* 可以适配，执行插入 */
		START_CRIT_SECTION();

		if (RelationNeedsWAL(fc_btree->index) && !fc_btree->isBuild)
		{
			XLogBeginInsert();
			XLogRegisterBuffer(0, fc_stack->buffer, REGBUF_STANDARD);
			if (BufferIsValid(fc_childbuf))
				XLogRegisterBuffer(1, fc_childbuf, REGBUF_STANDARD);
		}

		/* 执行页面更新，并注册任何额外的WAL数据 */
		fc_btree->execPlaceToPage(fc_btree, fc_stack->buffer, fc_stack,
							   fc_insertdata, fc_updateblkno, fc_ptp_workspace);

		MarkBufferDirty(fc_stack->buffer);

		/* 向内部页面插入完成了子页面的拆分。 */
		if (BufferIsValid(fc_childbuf))
		{
			GinPageGetOpaque(fc_childpage)->flags &= ~GIN_INCOMPLETE_SPLIT;
			MarkBufferDirty(fc_childbuf);
		}

		if (RelationNeedsWAL(fc_btree->index) && !fc_btree->isBuild)
		{
			XLogRecPtr	fc_recptr;
			ginxlogInsert fc_xlrec;
			BlockIdData fc_childblknos[2];

			fc_xlrec.flags = fc_xlflags;

			XLogRegisterData((char *) &fc_xlrec, sizeof(ginxlogInsert));

			/*
			 * 如果这是下行链接的插入，则记录有关子页面的信息。
			 */
			if (BufferIsValid(fc_childbuf))
			{
				BlockIdSet(&fc_childblknos[0], BufferGetBlockNumber(fc_childbuf));
				BlockIdSet(&fc_childblknos[1], GinPageGetOpaque(fc_childpage)->rightlink);
				XLogRegisterData((char *) fc_childblknos,
								 sizeof(BlockIdData) * 2);
			}

			fc_recptr = XLogInsert(RM_GIN_ID, XLOG_GIN_INSERT);
			PageSetLSN(fc_page, fc_recptr);
			if (BufferIsValid(fc_childbuf))
				PageSetLSN(fc_childpage, fc_recptr);
		}

		END_CRIT_SECTION();

		/* 插入已完成。 */
		fc_result = true;
	}
	else if (fc_rc == GPTP_SPLIT)
	{
		/*
		 * 不适合，需要拆分。新左页面和新右页面的拆分已在newlpage
		 * 和newrpage中计算，这些是指向palloc'd页面的指针，而不是
		 * 与缓冲区相关的。 stack->buffer尚未被触及。
		 */
		Buffer		fc_rbuffer;
		BlockNumber fc_savedRightLink;
		ginxlogSplit fc_data;
		Buffer		fc_lbuffer = InvalidBuffer;
		Page		fc_newrootpg = NULL;

		/* 获取一个新的索引页面以成为右页面 */
		fc_rbuffer = GinNewBuffer(fc_btree->index);

		/* 在索引构建过程中，计算新页面 */
		if (fc_buildStats)
		{
			if (fc_btree->isData)
				fc_buildStats->nDataPages++;
			else
				fc_buildStats->nEntryPages++;
		}

		fc_savedRightLink = GinPageGetOpaque(fc_page)->rightlink;

		/* 开始设置WAL记录 */
		fc_data.node = fc_btree->index->rd_node;
		fc_data.flags = fc_xlflags;
		if (BufferIsValid(fc_childbuf))
		{
			fc_data.leftChildBlkno = BufferGetBlockNumber(fc_childbuf);
			fc_data.rightChildBlkno = GinPageGetOpaque(fc_childpage)->rightlink;
		}
		else
			fc_data.leftChildBlkno = fc_data.rightChildBlkno = InvalidBlockNumber;

		if (fc_stack->parent == NULL)
		{
			/*
			 * 拆分根页面，因此我们需要为新的左页面分配新页面，并
			 * 在根页面上放置指向左和右页面的指针。
			 */
			fc_lbuffer = GinNewBuffer(fc_btree->index);

			/* 在索引构建过程中，计算新的左页面 */
			if (fc_buildStats)
			{
				if (fc_btree->isData)
					fc_buildStats->nDataPages++;
				else
					fc_buildStats->nEntryPages++;
			}

			fc_data.rrlink = InvalidBlockNumber;
			fc_data.flags |= GIN_SPLIT_ROOT;

			GinPageGetOpaque(fc_newrpage)->rightlink = InvalidBlockNumber;
			GinPageGetOpaque(fc_newlpage)->rightlink = BufferGetBlockNumber(fc_rbuffer);

			/*
			 * 构造一个新的根页面，包含指向新的左页面和右页面的下行链接。
			 * （在临时副本中执行此操作，而不是直接覆盖原始页面，因为
			 * 我们还没有进入临界区。）
			 */
			fc_newrootpg = PageGetTempPage(fc_newrpage);
			GinInitPage(fc_newrootpg, GinPageGetOpaque(fc_newlpage)->flags & ~(GIN_LEAF | GIN_COMPRESSED), BLCKSZ);

			fc_btree->fillRoot(fc_btree, fc_newrootpg,
							BufferGetBlockNumber(fc_lbuffer), fc_newlpage,
							BufferGetBlockNumber(fc_rbuffer), fc_newrpage);

			if (GinPageIsLeaf(BufferGetPage(fc_stack->buffer)))
			{

				PredicateLockPageSplit(fc_btree->index,
									   BufferGetBlockNumber(fc_stack->buffer),
									   BufferGetBlockNumber(fc_lbuffer));

				PredicateLockPageSplit(fc_btree->index,
									   BufferGetBlockNumber(fc_stack->buffer),
									   BufferGetBlockNumber(fc_rbuffer));
			}
		}
		else
		{
			/* 拆分非根页面 */
			fc_data.rrlink = fc_savedRightLink;

			GinPageGetOpaque(fc_newrpage)->rightlink = fc_savedRightLink;
			GinPageGetOpaque(fc_newlpage)->flags |= GIN_INCOMPLETE_SPLIT;
			GinPageGetOpaque(fc_newlpage)->rightlink = BufferGetBlockNumber(fc_rbuffer);

			if (GinPageIsLeaf(BufferGetPage(fc_stack->buffer)))
			{

				PredicateLockPageSplit(fc_btree->index,
									   BufferGetBlockNumber(fc_stack->buffer),
									   BufferGetBlockNumber(fc_rbuffer));
			}
		}

		/*
		 * 好的，我们现在在临时副本中拥有左页面的新内容（newlpage），
		 * 同样还有新分配的右块的新内容。原始页面仍然未更改。
		 *
		 * 如果这是根拆分，我们还有一个临时页面，包含根的新内容。
		 */

		START_CRIT_SECTION();

		MarkBufferDirty(fc_rbuffer);
		MarkBufferDirty(fc_stack->buffer);

		/*
		 * 将临时副本恢复到真实缓冲区。
		 */
		if (fc_stack->parent == NULL)
		{
			/* 拆分根，三页需要更新 */
			MarkBufferDirty(fc_lbuffer);
			memcpy(fc_page, fc_newrootpg, BLCKSZ);
			memcpy(BufferGetPage(fc_lbuffer), fc_newlpage, BLCKSZ);
			memcpy(BufferGetPage(fc_rbuffer), fc_newrpage, BLCKSZ);
		}
		else
		{
			/* 正常拆分，仅需要更新两页 */
			memcpy(fc_page, fc_newlpage, BLCKSZ);
			memcpy(BufferGetPage(fc_rbuffer), fc_newrpage, BLCKSZ);
		}

		/* 如果传递了childbuf，我们还清除其INCOMPLETE_SPLIT标志 */
		if (BufferIsValid(fc_childbuf))
		{
			GinPageGetOpaque(fc_childpage)->flags &= ~GIN_INCOMPLETE_SPLIT;
			MarkBufferDirty(fc_childbuf);
		}

		/* 写入WAL记录 */
		if (RelationNeedsWAL(fc_btree->index) && !fc_btree->isBuild)
		{
			XLogRecPtr	fc_recptr;

			XLogBeginInsert();

			/*
			 * 我们只是对所有拆分页面进行全页图像拍摄。拆分
			 * 不够常见，因此不值得复杂化代码以提高效率。
			 */
			if (fc_stack->parent == NULL)
			{
				XLogRegisterBuffer(0, fc_lbuffer, REGBUF_FORCE_IMAGE | REGBUF_STANDARD);
				XLogRegisterBuffer(1, fc_rbuffer, REGBUF_FORCE_IMAGE | REGBUF_STANDARD);
				XLogRegisterBuffer(2, fc_stack->buffer, REGBUF_FORCE_IMAGE | REGBUF_STANDARD);
			}
			else
			{
				XLogRegisterBuffer(0, fc_stack->buffer, REGBUF_FORCE_IMAGE | REGBUF_STANDARD);
				XLogRegisterBuffer(1, fc_rbuffer, REGBUF_FORCE_IMAGE | REGBUF_STANDARD);
			}
			if (BufferIsValid(fc_childbuf))
				XLogRegisterBuffer(3, fc_childbuf, REGBUF_STANDARD);

			XLogRegisterData((char *) &fc_data, sizeof(ginxlogSplit));

			fc_recptr = XLogInsert(RM_GIN_ID, XLOG_GIN_SPLIT);

			PageSetLSN(fc_page, fc_recptr);
			PageSetLSN(BufferGetPage(fc_rbuffer), fc_recptr);
			if (fc_stack->parent == NULL)
				PageSetLSN(BufferGetPage(fc_lbuffer), fc_recptr);
			if (BufferIsValid(fc_childbuf))
				PageSetLSN(fc_childpage, fc_recptr);
		}
		END_CRIT_SECTION();

		/*
		 * 我们现在可以释放新页面的锁/针，但保持
		 * stack->buffer 锁定。childbuf 也不会被解锁。
		 */
		UnlockReleaseBuffer(fc_rbuffer);
		if (fc_stack->parent == NULL)
			UnlockReleaseBuffer(fc_lbuffer);

		/*
		 * 如果我们拆分了根节点，就完成了。否则，拆分在
		 * 新页面的下链插入到父节点之前是不完整的。
		 */
		fc_result = (fc_stack->parent == NULL);
	}
	else
	{
		elog(ERROR, "invalid return code from GIN beginPlaceToPage method: %d", fc_rc);
		fc_result = false;			/* 保持编译器安静 */
	}

	/* 清理临时上下文 */
	MemoryContextSwitchTo(fc_oldCxt);
	MemoryContextDelete(fc_tmpCxt);

	return fc_result;
}

/*
 * 通过将新页面的下链插入到父节点来完成拆分。
 *
 * 进入时，stack->buffer 是独占锁定的。
 *
 * 如果 freestack 为 true，那么在我们向上爬升树时，
 * 所有缓存都被释放和解锁，'stack' 被释放。否则，stack->buffer 会保持
 * 锁定，stack 不变，除了可能向右移动以找到
 * 页面正确的父节点。
 */
static void fc_ginFinishSplit(GinBtree fc_btree, GinBtreeStack *fc_stack, bool fc_freestack,
			   GinStatsData *fc_buildStats)
{
	Page		fc_page;
	bool		fc_done;
	bool		fc_first = true;

	/* 这个循环向上爬升堆栈直到插入完成 */
	do
	{
		GinBtreeStack *fc_parent = fc_stack->parent;
		void	   *fc_insertdata;
		BlockNumber fc_updateblkno;

		/* 搜索父节点以锁定 */
		LockBuffer(fc_parent->buffer, GIN_EXCLUSIVE);

		/*
		 * 如果父页面拆分不完整，则首先完成该拆分，
		 * 然后继续当前的拆分。
		 *
		 * 注意：我们必须完成我们遇到的 *所有* 不完整拆分，即使
		 * 我们必须向右移动。否则，我们可能将一个没有
		 * 下链的页面选为目标，进一步拆分会失败。
		 */
		if (GinPageIsIncompleteSplit(BufferGetPage(fc_parent->buffer)))
			fc_ginFinishOldSplit(fc_btree, fc_parent, fc_buildStats, GIN_EXCLUSIVE);

		/* 如果需要，向右移动 */
		fc_page = BufferGetPage(fc_parent->buffer);
		while ((fc_parent->off = fc_btree->findChildPtr(fc_btree, fc_page, fc_stack->blkno, fc_parent->off)) == InvalidOffsetNumber)
		{
			if (GinPageRightMost(fc_page))
			{
				/*
				 * 最右侧页面，但我们找不到父节点，我们应该使用
				 * 普通搜索...
				 */
				LockBuffer(fc_parent->buffer, GIN_UNLOCK);
				fc_ginFindParents(fc_btree, fc_stack);
				fc_parent = fc_stack->parent;
				Assert(fc_parent != NULL);
				break;
			}

			fc_parent->buffer = ginStepRight(fc_parent->buffer, fc_btree->index, GIN_EXCLUSIVE);
			fc_parent->blkno = BufferGetBlockNumber(fc_parent->buffer);
			fc_page = BufferGetPage(fc_parent->buffer);

			if (GinPageIsIncompleteSplit(BufferGetPage(fc_parent->buffer)))
				fc_ginFinishOldSplit(fc_btree, fc_parent, fc_buildStats, GIN_EXCLUSIVE);
		}

		/* 插入下链 */
		fc_insertdata = fc_btree->prepareDownlink(fc_btree, fc_stack->buffer);
		fc_updateblkno = GinPageGetOpaque(BufferGetPage(fc_stack->buffer))->rightlink;
		fc_done = fc_ginPlaceToPage(fc_btree, fc_parent,
							  fc_insertdata, fc_updateblkno,
							  fc_stack->buffer, fc_buildStats);
		pfree(fc_insertdata);

		/*
		 * 如果调用者请求释放堆栈，立即解锁并释放
		 * 子缓冲区。否则保持其固定和锁定，但如果我们
		 * 必须递归向上爬升树，我们可以解锁上层页面，仅
		 * 保持堆栈底部的页面锁定。
		 */
		if (!fc_first || fc_freestack)
			LockBuffer(fc_stack->buffer, GIN_UNLOCK);
		if (fc_freestack)
		{
			ReleaseBuffer(fc_stack->buffer);
			pfree(fc_stack);
		}
		fc_stack = fc_parent;

		fc_first = false;
	} while (!fc_done);

	/* 解锁父节点 */
	LockBuffer(fc_stack->buffer, GIN_UNLOCK);

	if (fc_freestack)
		freeGinBtreeStack(fc_stack);
}

/*
 * ginFinishSplit() 的一个入口点，用于当我们偶然遇到一个
 * 已存在的不完整拆分页面时，而不是完成我们刚刚自己
 * 进行的拆分。区别在于 stack->buffer 可能在进入时
 * 仅是共享锁定的，并将升级为独占模式。
 *
 * 注意：升级锁时会暂时释放它。在扫描中这样做
 * 不太好，因为并发的 VACUUM 可能会在我们不保持锁的时候
 * 删除该页面。不过在插入中是可以的，因为 VACUUM
 * 有不同的机制来防止它与插入并发运行。（即，它在
 * 根节点上保持一个清理锁。）
 */
static void fc_ginFinishOldSplit(GinBtree fc_btree, GinBtreeStack *fc_stack, GinStatsData *fc_buildStats, int fc_access)
{
	elog(DEBUG1, "finishing incomplete split of block %u in gin index \"%s\"",
		 fc_stack->blkno, RelationGetRelationName(fc_btree->index));

	if (fc_access == GIN_SHARE)
	{
		LockBuffer(fc_stack->buffer, GIN_UNLOCK);
		LockBuffer(fc_stack->buffer, GIN_EXCLUSIVE);

		if (!GinPageIsIncompleteSplit(BufferGetPage(fc_stack->buffer)))
		{
			/*
			 * 其他人已经在我们不持有锁的时候完成了拆分。
			 */
			return;
		}
	}

	fc_ginFinishSplit(fc_btree, fc_stack, false, fc_buildStats);
}

/*
 * 将一个值插入到由栈描述的树中。
 *
 * 要插入的值在'insertdata'中给出。其格式取决于
 * 这是一个入口树还是数据树，ginInsertValue只是将其
 * 传递给特定于树的回调函数。
 *
 * 在索引构建过程中，buildStats非空，所包含的计数器
 * 根据需要递增。
 *
 * 注意：传入的栈会被释放，就像通过freeGinBtreeStack一样。
 */
void ginInsertValue(GinBtree fc_btree, GinBtreeStack *fc_stack, void *fc_insertdata,
			   GinStatsData *fc_buildStats)
{
	bool		fc_done;

	/* 如果叶页未完全分裂，首先完成分裂 */
	if (GinPageIsIncompleteSplit(BufferGetPage(fc_stack->buffer)))
		fc_ginFinishOldSplit(fc_btree, fc_stack, fc_buildStats, GIN_EXCLUSIVE);

	fc_done = fc_ginPlaceToPage(fc_btree, fc_stack,
						  fc_insertdata, InvalidBlockNumber,
						  InvalidBuffer, fc_buildStats);
	if (fc_done)
	{
		LockBuffer(fc_stack->buffer, GIN_UNLOCK);
		freeGinBtreeStack(fc_stack);
	}
	else
		fc_ginFinishSplit(fc_btree, fc_stack, true, fc_buildStats);
}
